/**
 * 常用工具集
 * Copyright(C) 2016-2017 liumurong
 */

import * as path from 'path';
import * as fs from 'fs';
import * as iconv from 'iconv-lite';

/**
 * Tools
 */
export class LiUtil {

    private constructor() { }

    /**
     * 生成一个GUID
     * 长度32位
     */
    static guid() {
        return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
            let r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
            return v.toString(16);
        });
    };

    /**
     * 生成GUID
     * 长度32位
     */
    static eguid(upper: boolean = false) {
        return 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
            let r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
            return upper ? v.toString(16).toUpperCase() : v.toString(16);
        });
    };

    /**
    * 生成一个uid，长度只有12位。
    */
    static midGuid() {
        return 'xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
            let r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
            return v.toString(16);
        });
    };

    /**
    * 生成一个uid，长度只有8位。
    */
    static miniGuid() {
        return 'xxxxxxxx'.replace(/[xy]/g, function (c) {
            let r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
            return v.toString(16);
        });
    };

    /**
     * Date对象转为字符串
     * @param date 时间 
     * @param format 格式
     */
    static dateFormat(date: Date, format: string) {
        if (!date || !date.getMonth || !date.getSeconds) {
            return "";
        }
        let fmt = format || "yyyy-MM-dd HH:mm:ss";
        let o = {
            "M+": date.getMonth() + 1,                              //月份   
            "d+": date.getDate(),                                   //日   
            "H+": date.getHours(),                                  //小时   
            "m+": date.getMinutes(),                                //分钟   
            "s+": date.getSeconds(),                                //秒   
            "q+": Math.floor((date.getMonth() + 3) / 3),            //季度   
            "S": date.getMilliseconds()                             //毫秒   
        };
        if (/(y+)/.test(fmt))
            fmt = fmt.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length));
        for (let k in o)
            if (new RegExp("(" + k + ")").test(fmt))
                fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
        return fmt;
    };

    /**
     * 字符串解析为时间
     * @param date 时间字符串
     * @param format 格式
     */
    static dateParse(date: string, format: string) {
        // 年
        let yearExp = /y{1,4}/;
        let year = this.dateItemParse(yearExp, format, date);
        if (year < 100) {
            // 两位年数，默认为20xx年，所以此方法仅在本世纪有效
            year = parseInt("20" + year);
        }
        // 月
        let monthExp = /MM/;
        let month = this.dateItemParse(monthExp, format, date) - 1;      // 月份的表示默认加1，所以需要减去
        // 日
        let dayExp = /dd/;
        let day = this.dateItemParse(dayExp, format, date);
        //  小时-- 24С小时制
        let hourExp = /HH/;
        let hour = this.dateItemParse(hourExp, format, date);
        // 分
        let minExp = /mm/;
        let min = this.dateItemParse(minExp, format, date);
        // 秒
        let secondExp = /ss/;
        let second = this.dateItemParse(secondExp, format, date);
        // 返回时间对象
        return new Date(year, month, day, hour, min, second);
    };
    /*
     * 字符串解析为时间辅助方法
     * 根据正则解析[年，月，日，时，分，秒]中的具体一项。
     * @param date 时间字符串
     * */
    private static dateItemParse(exp: RegExp, format: string, date: string) {
        let item = exp.exec(format);
        if (item && item.length === 1) {
            let length = item[0].length;
            let itemValue = date.slice(item.index, item.index + length);
            return parseInt(itemValue);
        }
        return 0;
    };

    /*
     * 将target对象的属性合并到ori对象上
     * 只适用于简单对象
     * */
    static extend(ori: object, target: object) {
        if (ori && target) {
            for (let key in target) {
                ori[key] = target[key];
            };
        }
        return ori;
    };

    /**
     * 
     * @param {*目标对象} ori  
     * @param {*数据来源对象} target 
     * @param {*排除的属性项} exkey 
     */
    static minExtend(ori: object, target: object, exkey: string) {
        if (ori && target) {
            for (let key in target) {
                if (key !== exkey) {
                    ori[key] = target[key];
                }
            };
        }
        return ori;
    }

    /**
     * 获取对象的属性字段值，支持嵌套
     * 使用.号分割
    */
    static getPropertyValue(val: object, proname: string) {
        if (this.isNullOrUndefined(val)) {
            return null;
        }
        if (this.isNullOrUndefined(proname)) {
            return val;
        }
        let pros = proname.split(".");
        let proval = val;
        let index = 0;
        while (proval && index < pros.length) {
            proval = proval[pros[index]];
            index++;
        }
        return proval;
    };


    /*
    * 对象是否为数组
    * @param val 需要检测的对象
    */
    static isArray(val: any) {
        return Array.isArray(val);
    };

    /**
     *  判断对象是否为空
     * @param val 需要检测的对象
     *  */
    static isNull(val: any) {
        return val === null;
    };

    /**
     * 判断对象是否未定义
     * @param val 需要检测的对象
     */
    static isUndefined(val: any) {
        return val === undefined;
    };

    /**
     * 对象是否为空或者未定义
     * @param val 需要检测的对象
     */
    static isNullOrUndefined(val: any) {
        return val === null || val === undefined;
    };

    /**
     * 对象是否为字符串
     * @param val 需要检测的对象
     */
    static isString(val: any) {
        return typeof val === "string";
    };
    /**
     * 对象是否为数字
     * @param val 需要检测的对象
     */
    static isNumber(val: any) {
        return typeof val === "number";
    };

    /**
     * 对象是否为方法(函数)
     * @param val 需要检测的对象
     */
    static isFunction(val: any) {
        return typeof val === "function";
    };
    /**
     * 对象是否为布尔值
     * @param val 需要检测的对象
     */
    static isBoolean(val: any) {
        return typeof val === "boolean";
    };

    /**
     * 对象是否为对象(此处对象为数据类型)
     * @param val 需要检测的对象
     */
    static isObject(val: any) {
        return !this.isNullOrUndefined(val) && typeof val === "object";
    };
    /**
     * 判断对象是否为时间对象
     * @param val 需要检测的对象
     */
    static isDate(val: any) {
        return this.objectToString(val) === "[object Date]";
    };

    /**
     * 对象是否为正则表达式
     * @param val 需要检测的对象
     */
    static isRegExp(val: any) {
        return this.objectToString(val) === "[object RegExp]";
    };

    /**
     * 对象是否为异常类型实例
     * @param val 需要检测的对象
     */
    static isError(val: any) {
        return this.objectToString(val) === "[object Error]" || val instanceof Error;
    };

    /**
     * 对象转成字符串
     * 由于方法的调用发生在Object对象上，所以返回结果都是传入对象的类型字符串。
     * @param val 需要检测的对象
    */
    static objectToString(val: any) {
        return Object.prototype.toString.call(val);
    };

    /**
     * 字符串分割
     * 字符串中添加指定的分割符，结果还是一个完整的字符串。
     * @param str 需要处理的字符串
     * @param width 宽度
     * @param spaceReplacer 添加的分隔符
    */
    static divider(str: string, width: number, spaceReplacer: string): string {
        if (str.length > width) {
            let p = width;
            while (p > 0 && (str[p] != ' ' && str[p] != '-')) {
                p--;
            }
            if (p > 0) {
                let left;
                if (str.substring(p, p + 1) == '-') {
                    left = str.substring(0, p + 1);
                } else {
                    left = str.substring(0, p);
                }
                let right = str.substring(p + 1);
                return left + spaceReplacer + this.divider(right, width, spaceReplacer);
            }
        }
        return str;
    };

    /**
     * 截取字符串指定长度，并在后边添加省略号'...'
     * @param str 需要裁剪的字符串
     * @param length 保留的字符长度
    */
    static trunc(str: string, length: number): string {
        if (!str) {
            return "";
        }
        length = length || 0;
        return str.length > length ? str.substr(0, length - 1) + '...' : str.substr(0);
    };

    static kCustomPromisifiedSymbol = Symbol('util.promisify.custom');
    static kCustomPromisifyArgsSymbol = Symbol('customPromisifyArgs');

    static promisify(original: Function) {
        if (typeof original !== 'function') {
            throw new TypeError('ERR_INVALID_ARG_TYPE');
        }
        if (original[this.kCustomPromisifiedSymbol]) {
            const fn = original[this.kCustomPromisifiedSymbol];
            if (typeof fn !== 'function') {
                throw new TypeError('ERR_INVALID_ARG_TYPE');
            }
            Object.defineProperty(fn, this.kCustomPromisifiedSymbol, {
                value: fn, enumerable: false, writable: false, configurable: true
            });
            return fn;
        }

        // Names to create an object from in case the callback receives multiple
        // arguments, e.g. ['stdout', 'stderr'] for child_process.exec.
        const argumentNames = original[this.kCustomPromisifyArgsSymbol];

        function fn(...args: any[]) {
            return new Promise((resolve, reject) => {
                try {
                    original.call(this, ...args, (err: any, ...values: any[]) => {
                        if (err) {
                            reject(err);
                        } else if (argumentNames !== undefined && values.length > 1) {
                            const obj = {};
                            for (let i = 0; i < argumentNames.length; i++)
                                obj[argumentNames[i]] = values[i];
                            resolve(obj);
                        } else {
                            resolve(values[0]);
                        }
                    });
                } catch (err) {
                    reject(err);
                }
            });
        }

        Object.setPrototypeOf(fn, Object.getPrototypeOf(original));

        Object.defineProperty(fn, this.kCustomPromisifiedSymbol, {
            value: fn, enumerable: false, writable: false, configurable: true
        });

        let pNames = Object.getOwnPropertyNames(original);
        let descriptors: PropertyDescriptorMap = {};
        pNames.forEach((pname, idx) => {
            descriptors[pname] = Object.getOwnPropertyDescriptor(original, pname);
        });

        return Object.defineProperties(
            fn,
            descriptors
        );
    }

    /**
     * 将对象的属性名称全部改成小写
     * 原包含大写的属性名还继续存在
     * @param query 原对象
     */
    static lowerPropertyName(query: object): object {
        if (!query) {
            return query;
        }
        for (const key in query) {
            if (!query.hasOwnProperty || query.hasOwnProperty && query.hasOwnProperty(key)) {
                query[key.toLowerCase()] = query[key];
            }
        }
        return query;
    }

    /**
    * 读取目录下内容
    * @param dirpath 路径
    * @returns  Promise<string[]>  目录下内容
    */
    static readdir(dirpath: string): Promise<string[]> {
        return new Promise((resolve, reject) => {
            fs.readdir(dirpath, function (err, files) {
                if (err) {
                    reject(err);
                    return;
                }
                resolve(files);
            });
        });
    };

    /**
     * 获取文档状态
     * @param filepath 文档路径
     * @returns 文档状态
     */
    static stat(filepath: string): Promise<fs.Stats> {
        return new Promise((resolve, reject) => {
            fs.stat(filepath, function (err, stats) {
                if (err) {
                    reject(err);
                    return;
                }
                resolve(stats);
            });
        });
    };


    /**
     * 获取目录下内容
     * 只能获取到目录或者文件
     * @param dirpath 文件夹路径
     * @param isfile 是否获取文件。为true时，取到的只有文件，为false时，取到的只有目录(文件夹)
     */
    static async getDirContents(dirpath: string, isfile: boolean) {
        let reCtxs = [];
        let ctxs = await this.readdir(dirpath);
        for (let i = 0, length = ctxs.length; i < length; i++) {
            let ctxStat = await this.stat(path.join(dirpath, ctxs[i]));
            if (isfile && ctxStat.isFile() || !isfile && ctxStat.isDirectory()) {
                reCtxs.push(ctxs[i]);
            }
        }
        return reCtxs;
    }

    /**
     * 获取XML文件Encoding编码
     * @param content xml文件内容
     */
    static getXMLEncoding(content: Buffer) {
        if (!content) {
            return "";
        }
        let reg = /encoding="(.+)"/i
        let contentStr = content.slice(0, 200).toString();  // 取前200个字符
        let encodingStrs = reg.exec(contentStr);
        if (!encodingStrs || encodingStrs.length === 0) {
            return "";
        }
        return encodingStrs[0].substring(10, encodingStrs[0].length - 1).toLowerCase();
    }
    /**
     * xml文件转码
     * 无论什么格式，转码为UTF-8
     * 简单的只支持转出格式为字符串
     * @param content xml文件内容
     */
    static XMLEncode(content: Buffer): string {
        if (!content) {
            return "";
        }
        let encoding = this.getXMLEncoding(content);
        if (!encoding) {
            return content.toString();
        }
        if (encoding === "utf-8" || encoding === "utf8") {
            return content.toString();
        }
        return iconv.decode(content, encoding);
    }

    // /**
    //  * 
    //  * @param content 内容流
    //  */
    // static XMLEncodeStream(content: ReadableStream) {
    // }
}